﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace Com.Lancetinc.Evs.Controls
{
    public class NeoShader : ShaderEffect
    {
        public NeoShader()
        {
            var pixelShader = new PixelShader();
            try
            {
                // Load shader effect.
                string assemblyName = typeof(NeoShader).Assembly.GetName().Name;
                string uri = "pack://application:,,,/" + assemblyName + ";component/Controls/Neo/Effect.ps";

                pixelShader.UriSource = new Uri(uri, UriKind.RelativeOrAbsolute);
            }
            catch (Exception e)
            {
                // Show compile errors
                MessageBox.Show(e.ToString());
                Environment.Exit(1);
            }

            this.PixelShader = pixelShader;
            this.DdxUvDdyUvRegisterIndex = 9;

            // Update each value (required)
            UpdateShaderValue(InputProperty);
            UpdateShaderValue(ShadowOffsetXProperty);
            UpdateShaderValue(ShadowOffsetYProperty);
            UpdateShaderValue(ShadowBlurRadiusProperty);
            UpdateShaderValue(ShadowSpreadRadiusProperty);
            UpdateShaderValue(ShadowBorderRadiusProperty);
            UpdateShaderValue(ShadowPrimaryColorProperty);
            UpdateShaderValue(ShadowSecondaryColorProperty);
            UpdateShaderValue(ShadowInsetProperty);
            UpdateShaderValue(ShadowRenderingModeProperty);

        }

        ////////////////////////   Input   ////////////////////////

        public Brush Input
        {
            get => (Brush)GetValue(InputProperty);
            set => SetValue(InputProperty, value);
        }

        public static readonly DependencyProperty InputProperty =
            RegisterPixelShaderSamplerProperty("Input", typeof(NeoShader), 0);

        ////////////////////////   OffsetX   ////////////////////////
        /// <summary>
        /// double (default : 7.0)
        /// The horizontal offset of the shadow
        /// </summary>
        public double OffsetX
        {
            get => (double)GetValue(ShadowOffsetXProperty);
            set => SetValue(ShadowOffsetXProperty, value);
        }

        public static readonly DependencyProperty ShadowOffsetXProperty =
                DependencyProperty.Register(
                    nameof(OffsetX),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(7.0,
                        (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
                        {
                            OffsetXRegisterCallback(d, e);

                            if (d is NeoShader obj)
                                obj.OnShadowOffsetXChanged((double)e.NewValue);
                        })
                );

        static readonly PropertyChangedCallback OffsetXRegisterCallback = PixelShaderConstantCallback(0);

        protected void OnShadowOffsetXChanged(double newValue)
        {
            if (Inset == 0)
            {
                this.PaddingLeft = Math.Abs(newValue) + BlurRadius + SpreadRadius;
                this.PaddingRight = Math.Abs(newValue) + BlurRadius + SpreadRadius;
                this.PaddingTop = Math.Abs(OffsetY) + BlurRadius + SpreadRadius;
                this.PaddingBottom = Math.Abs(OffsetY) + BlurRadius + SpreadRadius;
            }
            else
            {
                this.PaddingLeft = 0;
                this.PaddingRight = 0;
                this.PaddingTop = 0;
                this.PaddingBottom = 0;
            }
        }

        ///////////////////////   OffsetY   ////////////////////////
        /// <summary>
        /// double (default : 5.0)
        /// The vertical offset of the shadow
        /// </summary>
        public double OffsetY
        {
            get => (double)GetValue(ShadowOffsetYProperty);
            set => SetValue(ShadowOffsetYProperty, value);
        }

        public static readonly DependencyProperty ShadowOffsetYProperty =
                DependencyProperty.Register(
                    nameof(OffsetY),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(5.0,
                        (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
                        {
                            OffsetYRegisterCallback(d, e);

                            if (d is NeoShader obj)
                                obj.OnShadowOffsetYChanged((double)e.NewValue);
                        })
                );

        static readonly PropertyChangedCallback OffsetYRegisterCallback = PixelShaderConstantCallback(1);

        protected void OnShadowOffsetYChanged(double newValue)
        {
            if (Inset == 0)
            {
                this.PaddingLeft = Math.Abs(OffsetX) + BlurRadius + SpreadRadius;
                this.PaddingRight = Math.Abs(OffsetX) + BlurRadius + SpreadRadius;
                this.PaddingTop = Math.Abs(newValue) + BlurRadius + SpreadRadius;
                this.PaddingBottom = Math.Abs(newValue) + BlurRadius + SpreadRadius;
            }
            else
            {
                this.PaddingLeft = 0;
                this.PaddingRight = 0;
                this.PaddingTop = 0;
                this.PaddingBottom = 0;
            }
        }

        ///////////////////////   BlurRadius   ////////////////////////
        /// <summary>
        /// double (default : 5.0)
        /// The radius of the shadow's blur effect
        /// </summary>
        public double BlurRadius
        {
            get => (double)GetValue(ShadowBlurRadiusProperty);
            set => SetValue(ShadowBlurRadiusProperty, value);
        }

        public static readonly DependencyProperty ShadowBlurRadiusProperty =
                DependencyProperty.Register(
                    nameof(BlurRadius),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(5.0,
                        (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
                        {
                            BlurRadiusRegisterCallback(d, e);

                            if (d is NeoShader obj)
                                obj.OnShadowBlurRadiusChanged((double)e.NewValue);
                        })
                );

        static readonly PropertyChangedCallback BlurRadiusRegisterCallback = PixelShaderConstantCallback(2);

        protected void OnShadowBlurRadiusChanged(double newValue)
        {
            if (Inset == 0)
            {
                this.PaddingLeft = Math.Abs(OffsetX) + newValue + SpreadRadius;
                this.PaddingRight = Math.Abs(OffsetX) + newValue + SpreadRadius;
                this.PaddingTop = Math.Abs(OffsetY) + newValue + SpreadRadius;
                this.PaddingBottom = Math.Abs(OffsetY) + newValue + SpreadRadius;
            }
            else
            {
                this.PaddingLeft = 0;
                this.PaddingRight = 0;
                this.PaddingTop = 0;
                this.PaddingBottom = 0;
            }
        }

        ///////////////////////   SpreadRadius   ////////////////////////
        /// <summary>
        /// double (default : 2.0)
        /// The radius of the shadow's spread effect
        /// </summary>
        public double SpreadRadius
        {
            get => (double)GetValue(ShadowSpreadRadiusProperty);
            set => SetValue(ShadowSpreadRadiusProperty, value);
        }

        public static readonly DependencyProperty ShadowSpreadRadiusProperty =
                DependencyProperty.Register(
                    nameof(SpreadRadius),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(2.0,
                        (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
                        {
                            SpreadRadiusRegisterCallback(d, e);

                            if (d is NeoShader obj)
                                obj.OnShadowSpreadRadiusChanged((double)e.NewValue);
                        })
                );

        static readonly PropertyChangedCallback SpreadRadiusRegisterCallback = PixelShaderConstantCallback(3);

        protected void OnShadowSpreadRadiusChanged(double newValue)
        {
            if (Inset == 0)
            {
                this.PaddingLeft = Math.Abs(OffsetX) + BlurRadius + newValue;
                this.PaddingRight = Math.Abs(OffsetX) + BlurRadius + newValue;
                this.PaddingTop = Math.Abs(OffsetY) + BlurRadius + newValue;
                this.PaddingBottom = Math.Abs(OffsetY) + BlurRadius + newValue;
            }
            else
            {
                this.PaddingLeft = 0;
                this.PaddingRight = 0;
                this.PaddingTop = 0;
                this.PaddingBottom = 0;
            }
        }

        ///////////////////////   BorderRadius   ////////////////////////
        /// <summary>
        /// double (default : 0.0)
        /// The radius of the border
        /// </summary>
        public double BorderRadius
        {
            get => (double)GetValue(ShadowBorderRadiusProperty);
            set => SetValue(ShadowBorderRadiusProperty, value);
        }

        public static readonly DependencyProperty ShadowBorderRadiusProperty =
                DependencyProperty.Register(
                    nameof(BorderRadius),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(0.0, PixelShaderConstantCallback(4))
                );

        ////////////////////////   PrimaryColor   ////////////////////////
        /// <summary>
        /// Color (default : Colors.Silver)
        /// The primary color of the shadow
        /// </summary>
        public Color PrimaryColor
        {
            get => (Color)GetValue(ShadowPrimaryColorProperty);
            set => SetValue(ShadowPrimaryColorProperty, value);
        }

        public static readonly DependencyProperty ShadowPrimaryColorProperty =
            DependencyProperty.Register(
                nameof(PrimaryColor),
                typeof(Color),
                typeof(NeoShader),
                new UIPropertyMetadata(Colors.Silver, PixelShaderConstantCallback(5))
            );

        ////////////////////////   SecondaryColor   ////////////////////////
        /// <summary>
        /// Color (default : Colors.WhiteSmoke)
        /// The secondary color of the shadow
        /// </summary>
        public Color SecondaryColor
        {
            get => (Color)GetValue(ShadowSecondaryColorProperty);
            set => SetValue(ShadowSecondaryColorProperty, value);
        }

        public static readonly DependencyProperty ShadowSecondaryColorProperty =
            DependencyProperty.Register(
                nameof(SecondaryColor),
                typeof(Color),
                typeof(NeoShader),
                new UIPropertyMetadata(Colors.WhiteSmoke, PixelShaderConstantCallback(6))
            );

        ////////////////////////   Inset   ////////////////////////
        /// <summary>
        /// double (default : 0.0)
        /// Whether to changes the shadow to one inside the border
        /// expected: 0 or 1
        /// </summary>
        public double Inset
        {
            get => (double)GetValue(ShadowInsetProperty);
            set
            {
                try
                {
                    SetValue(ShadowInsetProperty, value);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
        }

        public static readonly DependencyProperty ShadowInsetProperty =
            DependencyProperty.Register(
                nameof(Inset),
                typeof(double),
                typeof(NeoShader),
                new UIPropertyMetadata(0.0,
                    (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
                    {
                        InsetRegisterCallback(d, e);

                        if (d is NeoShader obj)
                            obj.OnShadowInsetChanged((double)e.NewValue);
                    })
            );

        static readonly PropertyChangedCallback InsetRegisterCallback = PixelShaderConstantCallback(7);

        protected void OnShadowInsetChanged(double newValue)
        {
            if (newValue == 0)
            {
                this.PaddingLeft = Math.Abs(OffsetX) + BlurRadius + SpreadRadius;
                this.PaddingRight = Math.Abs(OffsetX) + BlurRadius + SpreadRadius;
                this.PaddingTop = Math.Abs(OffsetY) + BlurRadius + SpreadRadius;
                this.PaddingBottom = Math.Abs(OffsetY) + BlurRadius + SpreadRadius;
            }
            else
            {
                this.PaddingLeft = 0;
                this.PaddingRight = 0;
                this.PaddingTop = 0;
                this.PaddingBottom = 0;
            }
        }

        ////////////////////////   RenderingMode   ////////////////////////
        /// <summary>
        /// double (default : 0.0)
        /// Assign the rendering mode.
        /// 0: default mode;
        /// 1, 2, 3, ... : every mode;
        /// </summary>
        public double RenderingMode
        {
            get => (double)GetValue(ShadowRenderingModeProperty);
            set => SetValue(ShadowRenderingModeProperty, value);
        }

        public static readonly DependencyProperty ShadowRenderingModeProperty =
                DependencyProperty.Register(
                    nameof(RenderingMode),
                    typeof(double),
                    typeof(NeoShader),
                    new UIPropertyMetadata(0.0, PixelShaderConstantCallback(8))
                );
    }
}
